package com.zoe.onelink.actuate.autoconfigure;

import com.alibaba.druid.filter.FilterEventAdapter;
import com.alibaba.druid.pool.DruidDataSource;
import com.zoe.onelink.actuate.metrics.datasource.druid.DruidDataSourcePoolMetadata;
import com.zoe.onelink.actuate.metrics.datasource.druid.DruidMetricsFilter;
import com.zoe.onelink.actuate.metrics.http.HttpClientMetricsInterceptor;
import com.zoe.onelink.common.constant.OnelinkMetricsConstant;
import com.zoe.onelink.core.util.http.HttpClient;
import com.zoe.onelink.core.util.http.OnelinkOkHttpClient;
import com.zoe.onelink.logging.trace.http.util.TracedOnelinkHttpClient;
import com.zoe.onelink.util.SpringUtil;
import io.micrometer.core.instrument.Metrics;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.jdbc.DataSourceUnwrapper;
import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


/**
 * <p>标题: </p>
 * <p>描述: </p>
 * <p>版权: Copyright (c) 2022</p>
 * <p>公司: 智业软件股份有限公司</p>
 *
 * @version: 1.0
 * @author: ruansheng
 * @date: 2022-06-21
 */
@Configuration(proxyBeanMethods = false)
public class OnelinkMetricsAutoConfiguration {


    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(OnelinkOkHttpClient.class)
    static class OnelinkHttpClientMetricsAutoConfiguration implements InitializingBean {

        @Override
        public void afterPropertiesSet() {
            HttpClient httpClient = SpringUtil.getBean(HttpClient.class);
            if (!(httpClient instanceof OnelinkOkHttpClient)) {
                return;
            }
            OnelinkOkHttpClient onelinkOkHttpClient = (OnelinkOkHttpClient) httpClient;
            HttpClientMetricsInterceptor httpClientMetricsInterceptor = new HttpClientMetricsInterceptor(onelinkOkHttpClient.getDefaultHttpClient().dispatcher());
            onelinkOkHttpClient.mutate(okHttpClient ->
                    okHttpClient.newBuilder().addInterceptor(httpClientMetricsInterceptor).build()
            );
        }

    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(TracedOnelinkHttpClient.class)
    @RequiredArgsConstructor
    @Slf4j
    static class TracedOnelinkHttpClientMetricsAutoConfiguration implements InitializingBean {

        @Override
        public void afterPropertiesSet() {
            HttpClient httpClient = SpringUtil.getBean(HttpClient.class);
            if (!(httpClient instanceof TracedOnelinkHttpClient)) {
                return;
            }
            HttpClient delegateClient = ((TracedOnelinkHttpClient) httpClient).getDelegateClient();
            OnelinkOkHttpClient onelinkOkHttpClient = (OnelinkOkHttpClient) delegateClient;
            HttpClientMetricsInterceptor httpClientMetricsInterceptor = new HttpClientMetricsInterceptor(onelinkOkHttpClient.getDefaultHttpClient().dispatcher());
            onelinkOkHttpClient.mutate(okHttpClient ->
                    okHttpClient.newBuilder().addInterceptor(httpClientMetricsInterceptor).build()
            );
        }

    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({FilterEventAdapter.class})
    static class DruidMetricsAutoConfiguration {

        @Bean
        @ConditionalOnBean(DruidDataSource.class)
        public DruidMetricsFilter druidMetricsFilter() {
            return new DruidMetricsFilter();
        }

    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(DruidDataSource.class)
    static class DruidDataSourcePoolMetadataProviderConfiguration implements SmartInitializingSingleton {

        private DruidDataSource wrappedDataSource;

        @Bean
        public DataSourcePoolMetadataProvider druidPoolDataSourceMetadataProvider() {
            return (dataSource) -> {
                DruidDataSource ds = DataSourceUnwrapper.unwrap(dataSource, DruidDataSource.class);
                if (ds != null) {
                    this.wrappedDataSource = ds;
                    return new DruidDataSourcePoolMetadata(ds);
                }
                return null;
            };
        }

        @Override
        public void afterSingletonsInstantiated() {
            if (this.wrappedDataSource == null) {
                return;
            }
            Metrics.gauge(OnelinkMetricsConstant.DataSource.POOLING_COUNT, this.wrappedDataSource, DruidDataSource::getPoolingCount);
            Metrics.gauge(OnelinkMetricsConstant.DataSource.CONNECT_OPEN_COUNT, this.wrappedDataSource, DruidDataSource::getConnectCount);
            Metrics.gauge(OnelinkMetricsConstant.DataSource.CONNECT_CLOSE_COUNT, this.wrappedDataSource, DruidDataSource::getCloseCount);
            Metrics.gauge(OnelinkMetricsConstant.DataSource.CONNECT_ERROR_COUNT, this.wrappedDataSource, DruidDataSource::getConnectErrorCount);
            Metrics.gauge(OnelinkMetricsConstant.DataSource.EXECUTE_COUNT, this.wrappedDataSource, DruidDataSource::getExecuteCount);
            Metrics.gauge(OnelinkMetricsConstant.DataSource.ROLLBACK_COUNT, this.wrappedDataSource, DruidDataSource::getRollbackCount);
            Metrics.gauge(OnelinkMetricsConstant.DataSource.PHYSICAL_CONNECT_COUNT, this.wrappedDataSource, DruidDataSource::getCreateCount);
            Metrics.gauge(OnelinkMetricsConstant.DataSource.PHYSICAL_CLOSE_COUNT, this.wrappedDataSource, DruidDataSource::getDestroyCount);
            Metrics.gauge(OnelinkMetricsConstant.DataSource.PHYSICAL_CONNECT_ERROR_COUNT, this.wrappedDataSource, DruidDataSource::getCreateErrorCount);
            Metrics.gauge(OnelinkMetricsConstant.DataSource.NOT_EMPTY_WAIT_COUNT, this.wrappedDataSource, DruidDataSource::getNotEmptyWaitCount);
            Metrics.gauge(OnelinkMetricsConstant.DataSource.NOT_EMPTY_WAIT_MILLIS, this.wrappedDataSource, DruidDataSource::getNotEmptyWaitMillis);
        }

    }

}
